package org.metasketch.demo.model;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.metasketch.model.CommonDoubleIdentified;
import org.metasketch.simulator.petri.Link;

import com.thoughtworks.xstream.annotations.XStreamAlias;

@XStreamAlias("transaction-kind")
public class TransactionKind extends CommonDoubleIdentified {

	Set<TransactionStep> steps = new HashSet<TransactionStep>();

	// TransactionStep firstStep;
	@XStreamAlias("initiated-by")
	Set<ActorRole> initiatedBy = new HashSet<ActorRole>();

	@XStreamAlias("executed-by")
	Set<ActorRole> executedBy = new HashSet<ActorRole>();

	public TransactionKind() {
		CoordinationStep req = new CoordinationStep(
				CoordinationStepKind.Request);
		CoordinationStep promise = new CoordinationStep(
				CoordinationStepKind.Promise);

		ProductionStep prod = new ProductionStep();

		CoordinationStep state = new CoordinationStep(
				CoordinationStepKind.State);
		CoordinationStep accept = new CoordinationStep(
				CoordinationStepKind.Accept);

		req.setParentTransaction(this);
		promise.setParentTransaction(this);
		prod.setParentTransaction(this);
		state.setParentTransaction(this);
		accept.setParentTransaction(this);

		steps.add(req);
		steps.add(promise);

		steps.add(prod);

		steps.add(state);
		steps.add(accept);

		CoordinationLink first = new CoordinationLink();
		CoordinationLink second = new CoordinationLink();
		CoordinationLink third = new CoordinationLink();
		CoordinationLink fourth = new CoordinationLink();

		first.setSource(req);
		first.setTarget(promise);
		req.getOwnedCoordinationLinks().add(first);

		second.setSource(promise);
		second.setTarget(prod);
		promise.getOwnedCoordinationLinks().add(second);

		third.setSource(prod);
		third.setTarget(state);
		prod.getOwnedCoordinationLinks().add(third);

		fourth.setSource(state);
		fourth.setTarget(accept);
		state.getOwnedCoordinationLinks().add(fourth);

	}

	// management of composition links between transaction kinds
	public void createCompleteCompositionLink(TransactionKind child) {
		CoordinationStep pm = getFirstCoordinationStep(CoordinationStepKind.Promise);
		CoordinationLink linkToChild = new CoordinationLink();
		linkToChild.setSource(pm);

		CoordinationStep req = child
				.getFirstCoordinationStep(CoordinationStepKind.Request);

		linkToChild.setTarget(req);

		pm.getOwnedCoordinationLinks().add(linkToChild);

		CoordinationStep acc = child
				.getFirstCoordinationStep(CoordinationStepKind.Accept);
		CoordinationLink linkFromChild = new CoordinationLink();
		linkFromChild.setSource(acc);

		ProductionStep prod = getProductionStep();
		linkFromChild.setTarget(prod);

		acc.getOwnedCoordinationLinks().add(linkFromChild);

	}

	public void removeCompleteEnterCompositionLink(TransactionKind child) {

		// remove link to child
		CoordinationStep pm = getFirstCoordinationStep(CoordinationStepKind.Promise);

		CoordinationStep req = child
				.getFirstCoordinationStep(CoordinationStepKind.Request);

		List<CoordinationLink> linksToRemove = new ArrayList<CoordinationLink>();

		Set<CoordinationLink> links = pm.getOwnedCoordinationLinks();
		for (CoordinationLink link : links) {
			if (link.getTarget() == req)
				linksToRemove.add(link);
		}

		links.removeAll(linksToRemove);

		// remove link from child
		CoordinationStep accept = child
				.getFirstCoordinationStep(CoordinationStepKind.Accept);
		ProductionStep prod = getProductionStep();

		linksToRemove.clear();

		links = accept.getOwnedCoordinationLinks();
		for (CoordinationLink link : links) {
			if (link.getTarget() == prod)
				linksToRemove.add(link);
		}

		links.removeAll(linksToRemove);

	}

	public void createEnterCompositionLink(TransactionKind child) {
		CoordinationStep pm = getFirstCoordinationStep(CoordinationStepKind.Promise);
		CoordinationLink linkToChild = new CoordinationLink();
		linkToChild.setSource(pm);

		CoordinationStep req = child
				.getFirstCoordinationStep(CoordinationStepKind.Request);

		linkToChild.setTarget(req);

		pm.getOwnedCoordinationLinks().add(linkToChild);

	}

	public void removeEnterCompositionLink(TransactionKind child) {

		// remove link to child
		CoordinationStep pm = getFirstCoordinationStep(CoordinationStepKind.Promise);

		CoordinationStep req = child
				.getFirstCoordinationStep(CoordinationStepKind.Request);

		List<CoordinationLink> linksToRemove = new ArrayList<CoordinationLink>();

		Set<CoordinationLink> links = pm.getOwnedCoordinationLinks();
		for (CoordinationLink link : links) {
			if (link.getTarget() == req)
				linksToRemove.add(link);
		}

		links.removeAll(linksToRemove);

	}

	public void createExitCompositionLink(TransactionKind child) {
		// CoordinationStep pm =
		// getFirstCoordinationStep(CoordinationStepKind.Promise);
		// CoordinationLink linkToChild = new CoordinationLink();
		// linkToChild.setSource(pm);
		//
		// CoordinationStep req = child
		// .getFirstCoordinationStep(CoordinationStepKind.Request);
		//
		// linkToChild.setTarget(req);
		//
		// pm.getOwnedCoordinationLinks().add(linkToChild);

		CoordinationStep acc = child
				.getFirstCoordinationStep(CoordinationStepKind.Accept);
		CoordinationLink linkFromChild = new CoordinationLink();
		linkFromChild.setSource(acc);

		ProductionStep prod = getProductionStep();
		linkFromChild.setTarget(prod);

		acc.getOwnedCoordinationLinks().add(linkFromChild);

	}

	public void removeExitCompositionLink(TransactionKind child) {

		// // remove link to child
		// CoordinationStep pm =
		// getFirstCoordinationStep(CoordinationStepKind.Promise);
		//
		// CoordinationStep req = child
		// .getFirstCoordinationStep(CoordinationStepKind.Request);
		//
		// List<CoordinationLink> linksToRemove = new
		// ArrayList<CoordinationLink>();
		//
		// Set<CoordinationLink> links = pm.getOwnedCoordinationLinks();
		// for (CoordinationLink link : links) {
		// if (link.getTarget() == req)
		// linksToRemove.add(link);
		// }
		//
		// links.removeAll(linksToRemove);

		// remove link from child
		CoordinationStep accept = child
				.getFirstCoordinationStep(CoordinationStepKind.Accept);
		ProductionStep prod = getProductionStep();

		List<CoordinationLink> linksToRemove = new ArrayList<CoordinationLink>();

		Set<CoordinationLink> links = accept.getOwnedCoordinationLinks();
		for (CoordinationLink link : links) {
			if (link.getTarget() == prod)
				linksToRemove.add(link);
		}

		links.removeAll(linksToRemove);

	}

	// management of conditional links between transaction kinds

	public void createConditionalLink(TransactionKind child) {
		// add link to child
		CoordinationStep acc = getFirstCoordinationStep(CoordinationStepKind.Accept);

		CoordinationLink linkToChild = new CoordinationLink();
		linkToChild.setConditional(true);

		linkToChild.setSource(acc);

		CoordinationStep req = child
				.getFirstCoordinationStep(CoordinationStepKind.Request);

		linkToChild.setTarget(req);

		acc.getOwnedCoordinationLinks().add(linkToChild);

	}

	public void removeConditionalLink(TransactionKind child) {
		CoordinationStep acc = getFirstCoordinationStep(CoordinationStepKind.Accept);

		CoordinationStep req = child
				.getFirstCoordinationStep(CoordinationStepKind.Request);

		List<CoordinationLink> linksToRemove = new ArrayList<CoordinationLink>();

		Set<CoordinationLink> links = acc.getOwnedCoordinationLinks();
		for (CoordinationLink link : links) {
			if (link.getTarget() == req)
				linksToRemove.add(link);
		}

		links.removeAll(linksToRemove);

	}

	public CoordinationStep getFirstCoordinationStep(CoordinationStepKind kind) {
		for (TransactionStep step : steps) {
			if (step instanceof CoordinationStep) {
				CoordinationStep coord = (CoordinationStep) step;
				if (coord.getKind() == kind)
					return coord;
			}
		}
		return null;
	}

	public ProductionStep getProductionStep() {
		for (TransactionStep step : steps) {
			if (step instanceof ProductionStep)
				return (ProductionStep) step;
		}
		return null;
	}

	public List<CoordinationStep> getAllCoordinationStep(
			CoordinationStepKind kind) {
		List<CoordinationStep> selection = new ArrayList<CoordinationStep>();
		for (TransactionStep step : steps) {
			if (step instanceof CoordinationStep) {
				selection.add((CoordinationStep) step);
			}
		}
		return selection;
	}

	public List<TransactionStep> getActorRoleSteps(ActorRole role) {
		List<TransactionStep> selection = new ArrayList<TransactionStep>();
		for (TransactionStep step : steps) {
			if (step.getActorRole() == role) {
				selection.add((CoordinationStep) step);
			}
		}
		return selection;
	}

	public void setInitiateActorRole(ActorRole actorRole) {
		for (TransactionStep step : steps) {
			if (step instanceof CoordinationStep) {
				CoordinationStep coord = (CoordinationStep) step;
				if (coord.getKind() == CoordinationStepKind.Request)
					coord.setActorRole(actorRole);
				else if (coord.getKind() == CoordinationStepKind.Accept)
					coord.setActorRole(actorRole);
			}
		}
	}

	public void setExecuteActorRole(ActorRole actorRole) {
		for (TransactionStep step : steps) {
			if (step instanceof CoordinationStep) {
				CoordinationStep coord = (CoordinationStep) step;
				if (coord.getKind() == CoordinationStepKind.Promise)
					coord.setActorRole(actorRole);
				else if (coord.getKind() == CoordinationStepKind.State)
					coord.setActorRole(actorRole);

			} else if (step instanceof ProductionStep) {
				step.setActorRole(actorRole);
			}
		}
	}

	public List<CoordinationLink> getAllLinks(TransactionKind transaction) {
		List<CoordinationLink> selection = new ArrayList<CoordinationLink>();

		for (TransactionStep step : steps) {
			Set<CoordinationLink> links = step.getOwnedCoordinationLinks();
			for (CoordinationLink link : links) {
				if (link.getTarget().getParentTransaction() == transaction)
					selection.add(link);
			}
		}
		return selection;
	}

	public List<CoordinationLink> getAllNonAcceptLinks(
			TransactionKind transaction) {
		List<CoordinationLink> selection = new ArrayList<CoordinationLink>();

		for (TransactionStep step : steps) {
			// disregard final connections to same level or parent transactions,
			// only to child ones...this will be wrong in case of support for
			// more than the basic transaction pattern...
			if (step instanceof CoordinationStep
					&& ((CoordinationStep) step).getKind() == CoordinationStepKind.Accept)
				continue;

			Set<CoordinationLink> links = step.getOwnedCoordinationLinks();
			for (CoordinationLink link : links) {
				if (link.getTarget().getParentTransaction() == transaction)
					selection.add(link);
			}
		}
		return selection;
	}

	public List<TransactionKind> getAllNonAcceptLinks() {
		List<TransactionKind> selection = new ArrayList<TransactionKind>();

		for (TransactionStep step : steps) {
			// disregard final connections to same level or parent transactions,
			// only to child ones...this will be wrong in case of support for
			// more than the basic transaction pattern...
			if (step instanceof CoordinationStep
					&& ((CoordinationStep) step).getKind() == CoordinationStepKind.Accept)
				continue;

			Set<CoordinationLink> links = step.getOwnedCoordinationLinks();
			for (CoordinationLink link : links) {
				TransactionKind externalTransaction = link.getTarget()
						.getParentTransaction();
				if (externalTransaction != this
						&& !selection.contains(externalTransaction))
					selection.add(externalTransaction);
			}
		}
		return selection;
	}

	public Set<TransactionStep> getSteps() {
		return steps;
	}

	public void setSteps(Set<TransactionStep> steps) {
		this.steps = steps;
	}

	// public TransactionStep getFirstStep() {
	// return firstStep;
	// }
	//
	// public void setFirstStep(TransactionStep firstStep) {
	// this.firstStep = firstStep;
	// }

	public Set<ActorRole> getInitiatedBy() {
		return initiatedBy;
	}

	public void setInitiatedBy(Set<ActorRole> initiatedBy) {
		this.initiatedBy = initiatedBy;
	}

	public Set<ActorRole> getExecutedBy() {
		return executedBy;
	}

	public void setExecutedBy(Set<ActorRole> executedBy) {
		this.executedBy = executedBy;
	}

}
